/*
 * CategorySim.java - Stores similar classes or attributes for Wiki node Article or Category.
 *
 * Copyright (c) 2006 Computer-Aided Integrated Systems Laboratory, 
 * St. Petersburg Institute for Informatics and Automation RAS http://cais.lisa.iias.spb.su
 */

package topindex;

import matching.LevenshteinDistance;
import wikipedia.util.*;
import wikipedia.sql.PageNamespace;

import Ontology.*;
import java.util.*;

public class CategorySim {
    
    // Namespace 0-article, 14-category
    PageNamespace   ns; // byte
    
    
    /** Category or Article name */
    public String   title;
    
    /** Category or Article unique ID from MySQL Wikipedia */
    public int      id;     // category_id;
    
    /** Source article(s) for which this category was assigned */
    public ArrayList<String> article;
    
    /** Titles of classes which are similar to category, i.e. class_dist is small enough */
    //public String[] class_title;
    /** Distance between category and class_title[i] */
    //public int[]    class_dist;
    
    public CategorySim(String article_or_category_title, PageNamespace ns) {
        this.ns = ns;
        title = article_or_category_title;
        article = new ArrayList<String>();
    }

    public static final byte WD_CLASS       = 1;    // WebDeso class
    public static final byte WD_ATTRIBUTE   = 2;
    public class TitleDist {
        /** type could be WD_CLASS or WD_ATTRIBUTE */
        byte        type;
        
        /** title of similar class */
        String      title;
        
        /** distance between category and this similar class */
        Integer     dist;
        
    };
    /** Similar classes and attribues. */
    TitleDist[]     sims;
    
    /** Total distance between document and set of similar classes, it belongs to interval (0,1) */
    //public float    dist_total;
    
    
    /** First simple formula: similarity=1/1 + Sum{distances}.
     */
    /*public static double calcDistTotal1(Map<String, CategorySim> category_sim) {
        double sum = 1.0;
        
        if(null == category_sim) {
            System.out.println("Warning! category_sim is null in CategorySim.calcDistTotal1");
        } else {
            for(CategorySim cs:category_sim.values())
            {
                if(null != cs.sims) {
                    for(TitleDist td:cs.sims) {
                        sum += td.dist;
                    }
                }
            }
        }
        return 1.0 / sum;
    }*/
    
    /** Calculates similarity between (1) wikipedia article and categories and 
     * (2) ontology classes and attributes names.
     * Returns 1 means high similarity, 0 - similarity is absent.
     * 
     * Second formula: 1)sim = ((1 – k) ∙ |Dpair|1 / (|Csmax|∙|ONT|) +
                                     k  ∙ (1 – d_sum / (Dmax∙ |Dpair|)))/2
     * where 
     * D_pair is the array of Levenshtein distances between (1) names of ontology classes, 
     * attributes and (2) titles of articles, categories of Wikipedia.,
     * ONT – set of names of classes and attributes from ontology.
     *
     * @param k1       weight coefficient in [0,1]
     * @param k2       weight coefficient in [0,1]
     * @param d_max    maximum distance between similar words
     * @param  ont_n    number of classes and attributes in ontology
     * @param  cat_n    number of categories in Wikipedia (|CSmax|)
     */
    public static double calcDistTotal2(Map<String, CategorySim> category_sim,
                            double k,double d_max,
                            int ont_n, int cat_n) {
        double d_sum = 0.0; // total Levenshtein distance
        int n_pairs = 0;    // number of found valid Levenshtein distances between 
                            //  (1) names of ontology classes, attributes and 
                            //  (2) titles of articles, categories of Wikipedia.
        
        if(null == category_sim) {
            System.out.println("Warning! category_sim is null in CategorySim.calcDistTotal1");
        } else {
            for(CategorySim cs:category_sim.values())
            {
                if(null != cs.sims) {
                    for(TitleDist td:cs.sims) {
                        d_sum += td.dist;
                        n_pairs ++;
                    }
                }
            }
        }
        
        if(0 == n_pairs) 
            return 0.0;
        return 1.0 - ((1.0-k)*n_pairs / (ont_n*cat_n) + k*(1 - d_sum / (d_max * n_pairs))) / 2.0;
    }
    
    /** Searches similar classes to the category using LevenshteinDistance. 
     * Names of similar classes will be stored to class_title[], 
     * distances will be stored to class_dist[].
     *
     *  @param n_neighbours     maximum number of similar classes and attributes to extract and store.
     *  @param lev              LevenshteinDistance object contains stop words and distance matrix (to constraint Levenshtein similarity)
     */
    public void searchSimilarClassesAttributes(Hashtable htClasses, Hashtable htAttributes,
                                                int n_neighbours, LevenshteinDistance lev) {
        int i;
        int n = 0;
        
        if(null != htClasses)   { n += htClasses.keySet().size();   }
        if(null != htAttributes){ n += htAttributes.keySet().size();}
        
        if(1>n_neighbours || 1>n) return;
        
        List<TitleDist> title_dist = new ArrayList<TitleDist>(n);
        
        String[] words;
        words = StringUtil.split("_", title.toLowerCase()); // in Wiki delimiter = "_", e.g. Public_transportation
                
        // search similar classes
        if(null != htClasses) {
        for(Object id:htClasses.keySet()) {
            Classes c = (Classes)htClasses.get((Integer)id);
            TitleDist t = new TitleDist();
            t.title     = c.getClassName();
            t.type      = WD_CLASS;
            
            // variant 1. simple
            //t.dist  = LevenshteinDistance.computeLevenshteinDistance(t.title, title);
            
            // variant 2. complex
            // split title of node(category or article) to words, compare by Levenshtein, select minimum
            // in WebDeso (by space and dash) , e.g. "Television Station" or "Rail-cargo-transport"
            String tt = StringUtil.substChar(t.title.toLowerCase(), '-', ' ');
            String[] words2 = StringUtil.split(" ", tt); // delimiter = " ";
            
            t.dist  = lev.computeLevenshteinDistance(words, words2);
            
            if(-1 != t.dist && Integer.MAX_VALUE > t.dist) {
                title_dist.add(t);
            }
        }}
        
        // search similar attributes
        if(null != htAttributes) {
        for(Object id:htAttributes.keySet()) {
            Attributes c = (Attributes)htAttributes.get((Integer)id);
            TitleDist t = new TitleDist();
            t.title     = c.getAttrName();
            t.type      = WD_ATTRIBUTE;
            
            if(lev.hasPair(title,t.title)) {
                t.dist  = lev.getDistance(title,t.title);
            } else {
                
                // variant 1. simple
                //t.dist  = LevenshteinDistance.computeLevenshteinDistance(t.title, title);

                // variant 2. complex
                // split title of node(category or article) to words, compare by Levenshtein, select minimum
                String tt = StringUtil.substChar(t.title, '-', ' ');
                String[] words2 = StringUtil.split(" ", tt.toLowerCase()); // delimiter = " ";
                t.dist  = lev.computeLevenshteinDistance(words, words2);
                
                lev.put(title, t.title, t.dist);
            }

            if(-1 != t.dist && Integer.MAX_VALUE > t.dist) {
                title_dist.add(t);
            }
        }}
        
        Collections.sort(title_dist, CategorySim.DIST_ORDER);
        
        int n2 = title_dist.size();
        int max = (n_neighbours > n2) ? n2 : n_neighbours;
        sims = new TitleDist[max];
        
        for(i=0; i<max; i++) {
            sims[i] = new TitleDist();
            sims[i] = title_dist.get(i);
        }
    }
    
    public static final Comparator<TitleDist> DIST_ORDER = new Comparator<TitleDist>() {
        public int compare(TitleDist e1, TitleDist e2) {
            if (e1.dist <= e2.dist)
                return -1;
            return 1;
        }
    };
    
    public void printSimilarClassTitleDist () {
        for(int i=0; i<sims.length; i++) {
            System.out.println("dist="+sims[i].dist+", category(or article)="+sims[i].type +", Name="+sims[i].title);
        }
    }
}

//if(7>dist) {
            //    System.out.println("dist="+t.dist+", category_or_article_title="+title+", Class id="+(Integer)id+", Name="+t.title);
            //}